home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2007 December / PCWKCD1207B.iso / Blogowanie poza sfera / Flock 1.0 beta / flock-1.0RC3.en-US.win32.exe / flock / components / flockFeedManager.js < prev    next >
Text File  |  2007-10-18  |  69KB  |  2,238 lines

  1. // BEGIN FLOCK GPL
  2. // 
  3. // Copyright Flock Inc. 2005-2007
  4. // http://flock.com
  5. // 
  6. // This file may be used under the terms of of the
  7. // GNU General Public License Version 2 or later (the "GPL"),
  8. // http://www.gnu.org/licenses/gpl.html
  9. // 
  10. // Software distributed under the License is distributed on an "AS IS" basis,
  11. // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12. // for the specific language governing rights and limitations under the
  13. // License.
  14. // 
  15. // END FLOCK GPL
  16.  
  17. Components.utils.import("resource:///modules/FlockCryptoHash.jsm");
  18.  
  19. const FEED_CONTRACTID = '@flock.com/feed;1';
  20. const FEED_CLASSID    = Components.ID('2f7499f4-b47f-4350-a6c5-aee66520c720');
  21. const FEED_CLASSNAME  = 'Flock Feed';
  22.  
  23. const ITEM_CONTRACTID = '@flock.com/feed-item;1';
  24. const ITEM_CLASSID    = Components.ID('f97d9a24-2463-4e8d-b383-9e41f2bc60e8');
  25. const ITEM_CLASSNAME  = 'Flock Feed Item';
  26.  
  27. const FC_CONTRACTID   = '@flock.com/feed-context;1';
  28. const FC_CLASSID      = Components.ID('6e95c222-6707-42cf-aa1b-12baea3983e6');
  29. const FC_CLASSNAME    = 'Flock Feed Context';
  30.  
  31. const FF_CONTRACTID   = '@flock.com/feed-folder;1';
  32. const FF_CLASSID      = Components.ID('16b7ab70-c745-4f53-83a0-3375a031a113');
  33. const FF_CLASSNAME    = 'Flock Feed Folder';
  34.  
  35. const FM_CONTRACTID   = '@flock.com/feed-manager;1';
  36. const FM_CLASSID      = Components.ID('287848be-bbc9-42aa-953e-2ec916eb8d49');
  37. const FM_CLASSNAME    = 'Flock Feed Manager';
  38.  
  39.  
  40. const FEED_CONTENT_FILE      = 'feedcontent.sqlite';
  41.  
  42. const EXCERPT_MAX_WORDS      = 50;
  43. const TITLE_TRIM_MAX_CHARS   = 30;
  44.  
  45. const PURGE_RUN_INTERVAL     = 500;
  46. const PURGE_SLEEP_INTERVAL   = 1500;
  47.  
  48. const UNKNOWN_FEED_TYPE      = 'unknown';
  49.  
  50. const URI_FEED_PROPERTIES    = 'chrome://flock/locale/feeds/feeds.properties';
  51.  
  52. const URN_FEED_ROOT          = 'urn:flock:feedroot';
  53.  
  54. const NEWS_CONTEXT_NAME      = 'news';
  55. const LIVEMARKS_CONTEXT_NAME = 'livemarks';
  56.  
  57. const ATTR_URI_LIST          = ['action', 'href', 'src', 'longdesc', 'usemap',
  58.                                 'cite'];
  59.  
  60.  
  61. /* prefs */
  62. const PREF_EXPIRATION_TIME_BRANCH      = 'flock.feeds.expiration_time';
  63. const PREF_EXPIRATION_TIME             = '.subscriptions';
  64. const PREF_METADATA_EXPIRATION_TIME    = '.metadata_only';
  65.  
  66. const DEFAULT_EXPIRATION_TIME          = 60;
  67. const DEFAULT_METADATA_EXPIRATION_TIME = 24 * 60;
  68.  
  69. const FAVICON_EXPIRATION_TIME          = 24 * 60 * 60 * 1000;
  70.  
  71.  
  72. /* cardinal to danphe migration */
  73. const FLOCK_NS                   = 'http://flock.com/rdf#';
  74.  
  75. const OLD_FEEDS_RDF_FILE         = 'flock_subscriptions.rdf';
  76. const OLD_FEEDS_RDF_FILE_RELIC   = 'flock_subscriptions_old.rdf';
  77. const SUBSCRIPTIONS_RDF_ROOT     = 'urn:flock:feed:subscriptions';
  78.  
  79. const OLD_FLAGGED_RDF_FILE       = 'flock_feeds_flagged.rdf';
  80. const OLD_FLAGGED_RDF_FILE_RELIC = 'flock_feeds_flagged_old.rdf';
  81. const FLAGGED_RDF_ROOT           = 'urn:flock:feed';
  82.  
  83. const OLD_ROOT_RDF_FILE          = 'flock_feeds_root.rdf';
  84. const OLD_DISCOVERY_RDF_FILE     = 'flock_feeds_discovery.rdf';
  85.  
  86. const OLD_FEED_DATA_DIR          = 'feeds';
  87. const OLD_FEED_DATA_FILENAME     = 'feed.rdf';
  88. const OLD_FEED_DATA_RDF_PREFIX   = 'urn:flock:feed:';
  89. const OLD_FEED_DATA_POST_PREFIX  = OLD_FEED_DATA_RDF_PREFIX + 'post:';
  90.  
  91. const FORMAT_CONVERSIONS = { 'Atom 1.0': 'atom',
  92.                              'Atom 0.3': 'atom03',
  93.                              'RSS 2.0' : 'rss2',
  94.                              'RSS 1.0' : 'rss1',
  95.                              'RSS 0.9' : 'rss090',
  96.                              'RSS 0.91': 'rss091',
  97.                              'RSS 0.92': 'rss092',
  98.                              'RSS 0.93': 'rss093',
  99.                              'RSS 0.94': 'rss094',
  100.                            };
  101.  
  102.  
  103. const Cc = Components.classes;
  104. const Ci = Components.interfaces;
  105. const Cr = Components.results;
  106.  
  107. /* from nspr's prio.h */
  108. const PR_RDONLY      = 0x01;
  109. const PR_WRONLY      = 0x02;
  110. const PR_RDWR        = 0x04;
  111. const PR_CREATE_FILE = 0x08;
  112. const PR_APPEND      = 0x10;
  113. const PR_TRUNCATE    = 0x20;
  114. const PR_SYNC        = 0x40;
  115. const PR_EXCL        = 0x80;
  116.  
  117.  
  118. var gIOService     = null;
  119. var gFeedStorage   = null;
  120. var gReadMoreBlurb = null;
  121.  
  122.  
  123. function getObserverService() {
  124.   return Cc['@mozilla.org/observer-service;1']
  125.     .getService(Ci.nsIObserverService);
  126. }
  127.  
  128.  
  129. function excerptText(text, link) {
  130.   var words = text.split(/\s+/);
  131.  
  132.   if (words.length <= EXCERPT_MAX_WORDS)
  133.     return text;
  134.  
  135.   var excerpt = words.slice(0, EXCERPT_MAX_WORDS + 1).join(' ');
  136.  
  137.   if (link)
  138.     excerpt += '… <a href="' + link + '">' + gReadMoreBlurb + '</a>';
  139.  
  140.   return excerpt;
  141. }
  142.  
  143.  
  144. function checkEmpty(value) {
  145.   if (value == null) return null;
  146.   var str = value.toString();
  147.   return str ? str : null;
  148. }
  149.  
  150. function makeURI(uri) {
  151.   try {
  152.     return gIOService.newURI(uri, null, null);
  153.   }
  154.   catch (e) {
  155.     return null;
  156.   }
  157. }
  158.  
  159.  
  160. function getFeedNode(url, coop) {
  161.   var urn = coop.Feed.get_id({ URL: url });
  162.   return coop.get(urn);
  163. }
  164.  
  165.  
  166. function insertFavicon(feedNode) {
  167.   var age = Date.now() - feedNode.lastFaviconFetch.getTime();
  168.   if (age < FAVICON_EXPIRATION_TIME)
  169.     return;
  170.  
  171.   var url = makeURI(feedNode.link);
  172.   if (!url)
  173.     return;
  174.  
  175.   if (!/^https?/.test(url.scheme))
  176.     return;
  177.  
  178.   feedNode.lastFaviconFetch = new Date();
  179.  
  180.   var faviconURL = url.prePath + '/favicon.ico';
  181.  
  182.   var hr = Cc['@mozilla.org/xmlextras/xmlhttprequest;1']
  183.     .createInstance(Ci.nsIXMLHttpRequest);
  184.  
  185.   hr.onload = function(evt) { 
  186.     if (evt.target.status == 200)
  187.       feedNode.favicon = faviconURL;
  188.   };
  189.  
  190.   hr.backgroundRequest = true;
  191.   hr.open('HEAD', faviconURL);
  192.   hr.send(null);
  193. }
  194.  
  195. function getContextsForFeed(feedNode, coop) {
  196.   var contexts = [];
  197.   var parents = feedNode.getParents();
  198.   for each (var parent in parents) {
  199.     var node = parent;
  200.     while (!node.isInstanceOf(coop.FeedContext)) {
  201.       var nodeParents = node.getParents();
  202.       if (nodeParents.length)
  203.         node = nodeParents[0];
  204.       else
  205.         break;
  206.     }
  207.     if (node.isInstanceOf(coop.FeedContext))
  208.       contexts.push(node);
  209.   }
  210.   return contexts;
  211. }
  212.  
  213. function contextHasObject(contextName, obj, coop) {
  214.   if (obj.isInstanceOf(coop.FeedContext))
  215.     return obj.name == contextName;
  216.  
  217.   var parents = obj.getParents();
  218.   for each (var parent in parents) {
  219.     var node = parent;
  220.     while (!node.isInstanceOf(coop.FeedContext)) {
  221.       var nodeParents = node.getParents();
  222.       if (nodeParents.length)
  223.         node = nodeParents[0];
  224.       else
  225.         break;
  226.     }
  227.  
  228.     if (node.isInstanceOf(coop.FeedContext) && node.name == contextName)
  229.       return true;
  230.   }
  231.  
  232.   return false;
  233. }
  234.  
  235. function setFeedIndexable(feedNode, indexable) {
  236.   feedNode.isIndexable = indexable;
  237.  
  238.   var items = feedNode.children.enumerate();
  239.   while (items.hasMoreElements()) {
  240.     var item = items.getNext();
  241.     item.isIndexable = indexable;
  242.   }
  243. }
  244.  
  245. function updateNextRefresh(feedNode, coop) {
  246.   var contexts = getContextsForFeed(feedNode, coop);
  247.   if (contexts.length == 0)
  248.     return;
  249.  
  250.   var refreshInterval = contexts[0].refreshInterval;
  251.  
  252.   if (contexts.length > 1) {
  253.     for (var i = 1; i < contexts.length; i++) {
  254.       refreshInterval = Math.min(refreshInterval, contexts[i].refreshInterval);
  255.     }
  256.   }
  257.  
  258.   feedNode.nextRefresh = new Date(Date.now() + refreshInterval * 1000);
  259. }
  260.  
  261. function feedItemSorter(a, b) {
  262.   var res = a.datevalue - b.datevalue;
  263.   if (res == 0)
  264.     return b.indexvalue - a.indexvalue;
  265.   else
  266.     return res;
  267. }
  268.  
  269. function createStatement(dbconn, sql) {
  270.   var stmt = dbconn.createStatement(sql);
  271.   var wrapper = Cc["@mozilla.org/storage/statement-wrapper;1"]
  272.     .createInstance(Ci.mozIStorageStatementWrapper);
  273.  
  274.   wrapper.initialize(stmt);
  275.   return wrapper;
  276. }
  277.  
  278. function FeedStorage() {
  279.   var dbfile = Cc['@mozilla.org/file/directory_service;1']
  280.     .getService(Ci.nsIProperties).get('ProfD', Ci.nsIFile);
  281.   dbfile.append(FEED_CONTENT_FILE);
  282.  
  283.   var storageService = Cc['@mozilla.org/storage/service;1']
  284.     .getService(Ci.mozIStorageService);
  285.   this._DBConn = storageService.openDatabase(dbfile);
  286.  
  287.   var schema = 'id STRING PRIMARY KEY, feed STRING, ' +
  288.                'content STRING, excerpt STRING';
  289.  
  290.   try {
  291.     this._DBConn.createTable('feed_content', schema);
  292.   }
  293.   catch (e) { }
  294.  
  295.   this._queryContent = createStatement(this._DBConn,
  296.     'SELECT content FROM feed_content WHERE id = :id');
  297.   this._queryExcerpt = createStatement(this._DBConn,
  298.     'SELECT excerpt FROM feed_content WHERE id = :id');
  299.  
  300.   this._removeItem = createStatement(this._DBConn,
  301.     'DELETE FROM feed_content where id = :id');
  302.   this._insertItem = createStatement(this._DBConn,
  303.     'INSERT INTO feed_content (id, feed, content, excerpt) ' +
  304.     'VALUES (:id, :feed, :content, :excerpt)');
  305.  
  306.   this._removeFeed = createStatement(this._DBConn,
  307.     'DELETE FROM feed_content where feed = :feed');
  308. }
  309.  
  310. FeedStorage.prototype = {
  311.   deleteItem: function FS_deleteItem(id) {
  312.     this._removeItem.params.id = id;
  313.     this._removeItem.step();
  314.     this._removeItem.reset();
  315.   },
  316.   saveItem: function FS_saveItem(id, feed, content, excerpt) {
  317.     this.deleteItem(id);
  318.  
  319.     var pp = this._insertItem.params;
  320.     pp.id = id;
  321.     pp.feed = feed;
  322.     pp.content = content;
  323.     pp.excerpt = excerpt;
  324.     this._insertItem.step();
  325.     this._insertItem.reset();
  326.   },
  327.   deleteFeed: function FS_deleteFeed(feed) {
  328.     this._removeFeed.params.feed = feed;
  329.     this._removeFeed.step();
  330.     this._removeFeed.reset();
  331.   },
  332.   getContent: function FS_getContent(id) {
  333.     return this._getData(id, this._queryContent, 'content');
  334.   },
  335.   getExcerpt: function FS_getExcerpt(id) {
  336.     return this._getData(id, this._queryExcerpt, 'excerpt');
  337.   },
  338.   beginTransaction: function FS_beginTransation() {
  339.     this._DBConn.beginTransaction();
  340.   },
  341.   commitTransaction: function FS_commitTransation() {
  342.     this._DBConn.commitTransaction();
  343.   },
  344.   rollbackTransaction: function FS_rollbackTransation() {
  345.     this._DBConn.rollbackTransaction();
  346.   },
  347.   _getData: function FS__getData(id, stmt, field) {
  348.     stmt.reset();
  349.     stmt.params.id = id;
  350.  
  351.     var data = null;
  352.     if (stmt.step())
  353.       data = stmt.row[field];
  354.     stmt.reset();
  355.     return data;
  356.   }
  357. }
  358.  
  359. function Feed(feedNode, context, coop) {
  360.   this._feedNode = feedNode;
  361.   this._context = context;
  362.   this._coop = coop;
  363. }
  364.  
  365. Feed.prototype = {
  366.   getURL: function FEED_getURL() {
  367.     return makeURI(this._feedNode.URL);
  368.   },
  369.   getFinalURL: function FEED_getFinalURL() {
  370.     return makeURI(this._feedNode.finalURL);
  371.   },
  372.   getLink: function FEED_getLink() {
  373.     return makeURI(this._feedNode.link);
  374.   },
  375.   getType: function FEED_getType() {
  376.     return this._feedNode.format;
  377.   },
  378.   getTitle: function FEED_getTitle() {
  379.     return checkEmpty(this._feedNode.name);
  380.   },
  381.   getSubtitle: function FEED_getSubtitle() {
  382.     return checkEmpty(this._feedNode.subtitle);
  383.   },
  384.   getImage: function FEED_getImage() {
  385.     return makeURI(this._feedNode.image);
  386.   },
  387.   getFavicon: function FEED_getFavicon() {
  388.     return makeURI(this._feedNode.favicon);
  389.   },
  390.   getAuthor: function FEED_getAuthor() {
  391.     return checkEmpty(this._feedNode.author);
  392.   },
  393.   getPubDate: function FEED_getPubDate() {
  394.     return this._feedNode.datevalue;
  395.   },
  396.   getItemCount: function FEED_getItemCount() {
  397.     return this._feedNode.count;
  398.   },
  399.   getItem: function FEED_getItem(index) {
  400.     var items = this.getItems();
  401.     var i = 0;
  402.     while (items.hasMoreElements()) {
  403.       var item = items.getNext();
  404.       if (i == index)
  405.         return item;
  406.       i++;
  407.     }
  408.     return null;
  409.   },
  410.   getItems: function FEED_getItems() {
  411.     var filter = function(feed, item, coop) {
  412.       return new FeedItem(feed, item, coop);
  413.     };
  414.     return this._enumerateItems(filter);
  415.   },
  416.   refresh: function FEED_refresh() {
  417.     this._feedNode.nextRefresh = new Date();
  418.   },
  419.   getContext: function FEED_getContext() {
  420.     return this._context;
  421.   },
  422.   getUnreadCount: function FEED_getUnreadCount() {
  423.     return this._feedNode.unseenItems;
  424.   },
  425.   getUnreadItems: function FEED_getUnreadItems() {
  426.     var filter = function(feed, item, coop) {
  427.       if (item.unseen)
  428.         return new FeedItem(feed, item, coop);
  429.     };
  430.     return this._enumerateItems(filter);
  431.   },
  432.   getFlaggedItems: function FEED_getFlaggedItems() {
  433.     var filter = function(feed, item, coop) {
  434.       if (item.flagged)
  435.         return new FeedItem(feed, item, coop);
  436.     };
  437.     return this._enumerateItems(filter);
  438.   },
  439.   setTitle: function FEED_setTitle(title) {
  440.     this._feedNode.name = title;
  441.   },
  442.   markRead: function FEED_markRead() {
  443.     var items = this._feedNode.children.enumerate();
  444.     while (items.hasMoreElements()) {
  445.       var item = items.getNext();
  446.       item.unseen = false;
  447.     }
  448.     var countsPropagator = Cc['@flock.com/stream-counts-propagator;1']
  449.       .getService(Ci.flockIStreamCountsPropagator);
  450.     countsPropagator.syncCounts(this._feedNode.resource());
  451.   },
  452.   getFolder: function FEED_getFolder() {
  453.     if (this._context) {
  454.       var parents = this._feedNode.getParents();
  455.       if (parents.length == 1) {
  456.         return new FeedFolder(parents[0], this._context, this._coop);
  457.       } else if (parents.length > 1) {
  458.         var id = this._context._contextNode.id();
  459.         for each (var parent in parents) {
  460.           var node = parent;
  461.           while (!node.isInstanceOf(this._coop.FeedContext)) {
  462.             var nodeParents = node.getParents();
  463.             if (nodeParents.length)
  464.               node = nodeParents[0];
  465.             else
  466.               break;
  467.           }
  468.           if (node.isInstanceOf(this._coop.FeedContext) && node.id() == id)
  469.             return new FeedFolder(parent, this._context, this._coop);
  470.         }
  471.       }
  472.     }
  473.  
  474.     return null;
  475.   },
  476.   id: function FEED_id() {
  477.     return this._feedNode.id();
  478.   },
  479.  
  480.   _enumerateItems: function FEED__enumerateItems(filterFunc) {
  481.     var feed = this;
  482.     var coop = this._coop;
  483.     var items = this._feedNode.children.enumerate();
  484.  
  485.     var filteredEnumerator = {
  486.       next: null,
  487.       hasMoreElements: function() {
  488.         while (items.hasMoreElements()) {
  489.           this.next = filterFunc(feed, items.getNext(), coop);
  490.           if (this.next)
  491.             return true;
  492.         }
  493.         return false;
  494.       },
  495.       getNext: function() {
  496.         return this.next;
  497.       },
  498.     }
  499.  
  500.     return filteredEnumerator;
  501.   },
  502.  
  503.   getInterfaces: function FEED_getInterfaces(countRef) {
  504.     var interfaces = [Ci.flockIFeed, Ci.flockIFeedFolderItem,
  505.                       Ci.flockICoopObject, Ci.nsIClassInfo, Ci.nsISupports];
  506.     countRef.value = interfaces.length;
  507.     return interfaces;
  508.   },
  509.   getHelperForLanguage: function FEED_getHelperForLanguage(language) {
  510.     return null;
  511.   },
  512.   contractID: FEED_CONTRACTID,
  513.   classDescription: FEED_CLASSNAME,
  514.   classID: FEED_CLASSID,
  515.   implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
  516.  
  517.   QueryInterface: function FEED_QueryInterface(iid) {
  518.     if (iid.equals(Ci.flockIFeed) ||
  519.         iid.equals(Ci.flockIFeedFolderItem) ||
  520.         iid.equals(Ci.flockICoopObject) ||
  521.         iid.equals(Ci.nsIClassInfo) ||
  522.         iid.equals(Ci.nsISupports))
  523.       return this;
  524.     throw Cr.NS_ERROR_NO_INTERFACE;
  525.   }
  526. }
  527.  
  528.  
  529. function FeedItem(feed, itemNode, coop) {
  530.   this._feed = feed;
  531.   this._itemNode = itemNode;
  532.   this._coop = coop;
  533. }
  534.  
  535. FeedItem.prototype = {
  536.   getItemID: function FI_getItemID() {
  537.     return checkEmpty(this._itemNode.itemid);
  538.   },
  539.   getLink: function FI_getLink() {
  540.     return makeURI(this._itemNode.URL);
  541.   },
  542.   getTitle: function FI_getTitle() {
  543.     return checkEmpty(this._itemNode.name);
  544.   },
  545.   getPubDate: function FI_getPubDate() {
  546.     return this._itemNode.pubdate;
  547.   },
  548.   getCorrectedPubDate: function FI_getCorrectedPubDate() {
  549.     return this._itemNode.datevalue;
  550.   },
  551.   getAuthor: function FI_getAuthor() {
  552.     return checkEmpty(this._itemNode.author);
  553.   },
  554.   getContent: function FI_getContent() {
  555.     return gFeedStorage.getContent(this._itemNode.id());
  556.   },
  557.   getExcerpt: function FI_getExcerpt() {
  558.     return gFeedStorage.getExcerpt(this._itemNode.id());
  559.   },
  560.   getContext: function FI_getContext() {
  561.   },
  562.   isRead: function FI_isRead() {
  563.     return !this._itemNode.unseen;
  564.   },
  565.   isFlagged: function FI_isFlagged() {
  566.     return this._itemNode.flagged;
  567.   },
  568.   setRead: function FI_setRead(read) {
  569.     this._itemNode.unseen = !read;
  570.   },
  571.   setFlagged: function FI_setFlagged(flagged) {
  572.     if (this._itemNode.flagged == flagged)
  573.       return;
  574.  
  575.     this._itemNode.flagged = flagged;
  576.  
  577.     var feedNode = this._feed._feedNode;
  578.     var contexts = getContextsForFeed(feedNode, this._coop);
  579.  
  580.     if (flagged) {
  581.       for each (var context in contexts) {
  582.         context.flaggedItems.addItem(this._itemNode);
  583.       }
  584.     } else {
  585.       for each (var context in contexts) {
  586.         context.flaggedItems.children.remove(this._itemNode);
  587.       }
  588.     }
  589.   },
  590.   getFeed: function FI_getFeed() {
  591.     return this._feed;
  592.   },
  593.  
  594.   getInterfaces: function FI_getInterfaces(countRef) {
  595.     var interfaces = [Ci.flockIFeedItem, Ci.nsIClassInfo, Ci.nsISupports];
  596.     countRef.value = interfaces.length;
  597.     return interfaces;
  598.   },
  599.   getHelperForLanguage: function FI_getHelperForLanguage(language) {
  600.     return null;
  601.   },
  602.   contractID: ITEM_CONTRACTID,
  603.   classDescription: ITEM_CLASSNAME,
  604.   classID: ITEM_CLASSID,
  605.   implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
  606.  
  607.   QueryInterface: function FI_QueryInterface(iid) {
  608.     if (iid.equals(Ci.flockIFeedItem) ||
  609.         iid.equals(Ci.nsIClassInfo) ||
  610.         iid.equals(Ci.nsISupports))
  611.       return this;
  612.     throw Cr.NS_ERROR_NO_INTERFACE;
  613.   }
  614. }
  615.  
  616.  
  617. function FeedFolder(folderNode, context, coop) {
  618.   this._logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
  619.   this._logger.init('feedmanager');
  620.   this._logger.info('created folder object');
  621.  
  622.   this._folderNode = folderNode;
  623.   this._context = context;
  624.   this._coop = coop;
  625. }
  626.  
  627. FeedFolder.prototype = {
  628.   getTitle: function FF_getTitle() {
  629.     return this._folderNode.name;
  630.   },
  631.   getItemCount: function FF_getItemCount() {
  632.   },
  633.   getItems: function FF_getItems() {
  634.   },
  635.   setTitle: function FF_setTitle(title) {
  636.     this._folderNode.name = title;
  637.   },
  638.   getUnreadCount: function FF_getUnreadCount() {
  639.     return this._folderNode.unseenItems;
  640.   },
  641.   markRead: function FF_markRead() {
  642.     var children = this.getChildren();
  643.     while (children.hasMoreElements()) {
  644.       var child = children.getNext().QueryInterface(Ci.flockIFeedFolderItem);
  645.       child.markRead();
  646.     }
  647.   },
  648.   subscribeURL: function FF_subscribeURL(url, title) {
  649.     var feedObj = { URL: url, serviceId: FM_CONTRACTID };
  650.  
  651.     if (title) {
  652.       title = title.replace(/[\r\n]+/g, ' ');
  653.       this._logger.info('subscribing feed ' + url + ' with title ' + title);
  654.       feedObj.name = title;
  655.     } else {
  656.       this._logger.info('subscribing feed ' + url);
  657.     }
  658.  
  659.     var feedNode = new this._coop.Feed(feedObj);
  660.  
  661.     if (contextHasObject(NEWS_CONTEXT_NAME, this._folderNode, this._coop))
  662.       setFeedIndexable(feedNode, true);
  663.  
  664.     this._folderNode.children.addOnce(feedNode);
  665.  
  666.     if (feedNode.nextRefresh)
  667.       updateNextRefresh(feedNode, this._coop);    
  668.     else
  669.       feedNode.nextRefresh = new Date();
  670.  
  671.     feedNode.isPollable = true;
  672.  
  673.     var feed = new Feed(feedNode, null, this._coop);
  674.     this._context.notifyOnSubscribe(feed);
  675.  
  676.     return feed;
  677.   },
  678.   subscribeFeed: function FF_subscribeFeed(feed) {
  679.     var feedNode = this._subscriptionNode(feed, 'subscribing');
  680.  
  681.     if (contextHasObject(NEWS_CONTEXT_NAME, this._folderNode, this._coop))
  682.       setFeedIndexable(feedNode, true);
  683.  
  684.     this._folderNode.children.addOnce(feedNode);
  685.  
  686.     updateNextRefresh(feedNode, this._coop);    
  687.     feedNode.isPollable = true;
  688.  
  689.     this._context.notifyOnSubscribe(feed);
  690.   },
  691.   unsubscribeFeed: function FF_unsubscribeFeed(feed) {
  692.     var feedNode = this._subscriptionNode(feed, 'unsubscribing');
  693.  
  694.     this._folderNode.children.remove(feedNode);
  695.  
  696.     var contexts = getContextsForFeed(feedNode, this._coop);
  697.     if (contexts.length == 0)
  698.       feedNode.isPollable = false;
  699.  
  700.     if (contexts.length == 0 ||
  701.         !contextHasObject(NEWS_CONTEXT_NAME, feedNode, this._coop))
  702.       setFeedIndexable(feedNode, false);
  703.  
  704.     this._context.notifyOnUnsubscribe(feed);
  705.   },
  706.   subscribeFeedWithPosition:
  707.   function FF_subscribeFeedWithPosition(feed, target, orientation) {
  708.     var coop = this._coop;
  709.     var feedNode = this._subscriptionNode(feed, 'subscribing');
  710.  
  711.     var check, value;
  712.     if (target instanceof Ci.flockIFeedFolder) {
  713.       value = target.getTitle();
  714.       check = function(child) {
  715.         return child.isInstanceOf(coop.FeedFolder) && child.name == value;
  716.       }
  717.     } else {
  718.       if (orientation == Ci.flockIFeedFolder.ORIENT_INSIDE)
  719.         throw Cr.NS_ERROR_INVALID_ARG;
  720.  
  721.       value = target.getURL().spec;
  722.       check = function(child) {
  723.         return child.isInstanceOf(coop.Feed) && child.URL == value;
  724.       }
  725.     }
  726.  
  727.     var children = this._folderNode.children.enumerate();
  728.     while (children.hasMoreElements()) {
  729.       var node = children.getNext();
  730.       if (check(node)) {
  731.         switch (orientation) {
  732.           case Ci.flockIFeedFolder.ORIENT_INSIDE:
  733.              node.children.addOnce(feedNode);
  734.              break;
  735.           case Ci.flockIFeedFolder.ORIENT_ABOVE:
  736.              var container = this._folderNode.children;
  737.              container.insertAt(feedNode, container.indexOf(node));
  738.              break;
  739.           case Ci.flockIFeedFolder.ORIENT_BELOW:
  740.              var container = this._folderNode.children;
  741.              container.insertAt(feedNode, container.indexOf(node) + 1);
  742.              break;
  743.           default:
  744.              throw Cr.NS_ERROR_INVALID_ARG;
  745.              break;
  746.         }
  747.  
  748.         updateNextRefresh(feedNode, this._coop);    
  749.         feedNode.isPollable = true;
  750.  
  751.         if (contextHasObject(NEWS_CONTEXT_NAME, this._folderNode, this._coop))
  752.           setFeedIndexable(feedNode, true);
  753.  
  754.         this._context.notifyOnSubscribe(feed);
  755.         return;
  756.       }
  757.     }
  758.  
  759.     throw Components.Exception("Couldn't find feed");
  760.   },
  761.   addFolder: function FF_addFolder(title) {
  762.     this._logger.info('adding folder ' + title);
  763.     var children = this._folderNode.children.enumerate();
  764.     while (children.hasMoreElements()) {
  765.       var child = children.getNext();
  766.       if (child.name == title) {
  767.         var msg = 'Folder already exists: ' + title;
  768.         this._logger.error(msg);
  769.         throw Components.Exception(msg);
  770.       }
  771.     }
  772.     var folder = new this._coop.FeedFolder({ name: title });
  773.     this._folderNode.children.add(folder);
  774.     return new FeedFolder(folder, this._context, this._coop);
  775.   },
  776.   removeFolder: function FF_removeFolder(folder) {
  777.     var name = folder.getTitle();
  778.     this._logger.info('removing folder ' + name);
  779.     var children = this._folderNode.children.enumerate();
  780.     while (children.hasMoreElements()) {
  781.       var child = children.getNext();
  782.       if (child.name == name && child.isInstanceOf(this._coop.FeedFolder)) {
  783.         var childFolder = new FeedFolder(child, this._context, this._coop);
  784.         childFolder._destroySelf();
  785.         return;
  786.       }
  787.     }
  788.     this._logger.warn('folder ' + name + ' not found');
  789.   },
  790.   getChildFolder: function FF_getChildFolder(title) {
  791.     var children = this._folderNode.children.enumerate();
  792.     while (children.hasMoreElements()) {
  793.       var obj = children.getNext();
  794.       if (obj.isInstanceOf(this._coop.FeedFolder) && obj.name == title)
  795.         return new FeedFolder(obj, this._context, this._coop);
  796.     }
  797.     throw Components.Exception("Folder does not exist: " + title);
  798.   },
  799.   getChildren: function FF_getChildren() {
  800.     var coop = this._coop;
  801.     var context = this._context;
  802.  
  803.     var children = this._folderNode.children.enumerate();
  804.  
  805.     var enumerator = {
  806.       hasMoreElements: function() {
  807.         return children.hasMoreElements();
  808.       },
  809.       getNext: function() {
  810.         var obj = children.getNext();
  811.         if (obj.isInstanceOf(coop.FeedFolder))
  812.           return new FeedFolder(obj, context, coop)
  813.         else
  814.           return new Feed(obj, context, coop)
  815.       }
  816.     };
  817.  
  818.     return enumerator;
  819.   },
  820.   getFolder: function FF_getFolder() {
  821.     if (this._folderNode.isInstanceOf(this._coop.FeedContext))
  822.       return null;
  823.  
  824.     var parents = this._folderNode.getParents();
  825.     if (parents.length > 0)
  826.       return new FeedFolder(parents[0], this._context, this._coop);
  827.  
  828.     return null;
  829.   },
  830.  
  831.   _subscriptionNode: function FF___subscriptionNode(feed, action) {
  832.     var url = feed.getURL().spec;
  833.     this._logger.info(action + ' feed ' + url + ' from folder ' +
  834.                       this.getTitle());
  835.  
  836.     var feedNode = getFeedNode(url, this._coop);
  837.     if (!feedNode)
  838.       throw Cr.NS_ERROR_FAILURE;
  839.  
  840.     return feedNode;
  841.   },
  842.  
  843.   _destroySelf: function FF__destroySelf() {
  844.     var feeds = [];
  845.     var folders = [];
  846.  
  847.     var children = this._folderNode.children.enumerateBackwards();
  848.     while (children.hasMoreElements()) {
  849.       var child = children.getNext();
  850.       if (child.isInstanceOf(this._coop.FeedFolder)) {
  851.         var folder = new FeedFolder(child, this._context, this._coop);
  852.         folders.push(folder);
  853.       } else if (child.isInstanceOf(this._coop.Feed)) {
  854.         var feed = new Feed(child, this._context, this._coop);
  855.         feeds.push(feed);
  856.       }
  857.     }
  858.  
  859.     for each (var folder in folders)
  860.       folder._destroySelf();
  861.  
  862.     for each (var feed in feeds)
  863.       this.unsubscribeFeed(feed);
  864.  
  865.     var name = this._folderNode.name;
  866.     this._folderNode.destroy();
  867.  
  868.     this._logger.info('folder ' + name + ' removed');
  869.   },
  870.  
  871.   getInterfaces: function FF_getInterfaces(countRef) {
  872.     var interfaces = [Ci.flockIFeedFolder, Ci.flockIFeedFolderItem,
  873.                       Ci.nsIClassInfo, Ci.nsISupports];
  874.     countRef.value = interfaces.length;
  875.     return interfaces;
  876.   },
  877.   getHelperForLanguage: function FF_getHelperForLanguage(language) {
  878.     return null;
  879.   },
  880.   contractID: FF_CONTRACTID,
  881.   classDescription: FF_CLASSNAME,
  882.   classID: FF_CLASSID,
  883.   implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
  884.  
  885.   QueryInterface: function FF_QueryInterface(iid) {
  886.     if (iid.equals(Ci.flockIFeedFolder) ||
  887.         iid.equals(Ci.flockIFeedFolderItem) ||
  888.         iid.equals(Ci.nsIClassInfo) ||
  889.         iid.equals(Ci.nsISupports))
  890.       return this;
  891.     throw Cr.NS_ERROR_NO_INTERFACE;
  892.   }
  893. }
  894.  
  895. function FeedContext(contextNode, coop) {
  896.   this._logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
  897.   this._logger.init('feedmanager');
  898.   this._logger.info('created context for ' + contextNode.name);
  899.  
  900.   this._coop = coop;
  901.   this._contextNode = contextNode;
  902.  
  903.   this._observers = [];
  904. }
  905.  
  906. FeedContext.prototype = {
  907.   getName: function FC_getName() {
  908.     return this._contextNode.name;
  909.   },
  910.  
  911.   getRoot: function FC_getRoot() {
  912.     return new FeedFolder(this._contextNode, this, this._coop);
  913.   },
  914.  
  915.   getSubscription: function FC_getSubscription(url) {
  916.     if (!this.existsSubscription(url))
  917.       return null;
  918.  
  919.     var feedNode = getFeedNode(url.spec, this._coop);
  920.     return new Feed(feedNode, this, this._coop);
  921.   },
  922.   getSubscriptions: function FC_getSubscriptions() {
  923.     var context = this;
  924.     var coop = this._coop;
  925.     var feeds = this._coop.Feed.all();
  926.  
  927.     var filteredEnumerator = {
  928.       next: null,
  929.       hasMoreElements: function() {
  930.         while (feeds.hasMoreElements()) {
  931.           var feedNode = feeds.getNext();
  932.           if (contextHasObject(context._contextNode.name, feedNode, coop)) {
  933.             this.next = new Feed(feedNode, context, coop);
  934.             return true;
  935.           }
  936.         }
  937.         return false;
  938.       },
  939.       getNext: function() {
  940.         return this.next;
  941.       }
  942.     }
  943.    
  944.     return filteredEnumerator; 
  945.   },
  946.   existsSubscription: function FC_existsSubscription(url) {
  947.     var feedNode = getFeedNode(url.spec, this._coop);
  948.     if (!feedNode)
  949.       return false;
  950.  
  951.     return contextHasObject(this._contextNode.name, feedNode, this._coop);
  952.   },
  953.  
  954.   refresh: function FC_refresh() {
  955.     var feeds = this.getSubscriptions();
  956.     while (feeds.hasMoreElements()) {
  957.       var feed = feeds.getNext().QueryInterface(Ci.flockIFeed);
  958.       feed.refresh();
  959.     }
  960.   },
  961.  
  962.   getRefreshInterval: function FC_getRefreshInterval() {
  963.     return this._contextNode.refreshInterval / 60;
  964.   },
  965.   setRefreshInterval: function FC_setRefreshInterval(minutes) {
  966.     this._contextNode.refreshInterval = minutes * 60;
  967.   },
  968.   getFeedItemCap: function FC_getFeedItemCap() {
  969.   },
  970.   setFeedItemCap: function FC_setFeedItemCap(maxItems) {
  971.   },
  972.  
  973.   addObserver: function FC_addObserver(observer) {
  974.     function hasFunc(element, index, array) {
  975.       return element == observer;
  976.     }
  977.     if (!this._observers.some(hasFunc)) {
  978.       this._observers.push(observer);
  979.       if (this._observers.length == 1)
  980.         this._watchUnseenItems();
  981.     }
  982.   },
  983.   removeObserver: function FC_removeObserver(observer) {
  984.     function keepFunc(element, index, array) {
  985.       return element != observer;
  986.     }
  987.     this._observers = this._observers.filter(keepFunc);
  988.     if (this._observers.length == 0)
  989.       this._unwatchUnseenItems();
  990.   },
  991.  
  992.   _watchUnseenItems: function FC__watchUnseenItems() {
  993.     var RDFS = Cc['@mozilla.org/rdf/rdf-service;1']
  994.       .getService(Ci.nsIRDFService);
  995.     var unseenItems = RDFS.GetResource('http://flock.com/rdf#unseenItems')
  996.  
  997.     var faves = Cc['@mozilla.org/rdf/datasource;1?name=flock-favorites']
  998.       .getService(Ci.flockIRDFObservable);
  999.     faves.addArcObserver(Ci.flockIRDFObserver.TYPE_CHANGE,
  1000.                          this._contextNode.resource(), unseenItems,
  1001.                          null, this); 
  1002.   },
  1003.   _unwatchUnseenItems: function FC__unwatchUnseenItems() {
  1004.     var RDFS = Cc['@mozilla.org/rdf/rdf-service;1']
  1005.       .getService(Ci.nsIRDFService);
  1006.     var unseenItems = RDFS.GetResource('http://flock.com/rdf#unseenItems')
  1007.  
  1008.     var faves = Cc['@mozilla.org/rdf/datasource;1?name=flock-favorites']
  1009.       .getService(Ci.flockIRDFObservable);
  1010.     faves.removeArcObserver(Ci.flockIRDFObserver.TYPE_CHANGE,
  1011.                             this._contextNode.resource(), unseenItems,
  1012.                             null, this); 
  1013.   },
  1014.   rdfChanged: function FC_rdfChanged(ds, type, rsrc, pred, obj, oldObj) {
  1015.     for each (var observer in this._observers) {
  1016.       observer.onUnreadCountChange(this, this._contextNode.unseenItems);
  1017.     }
  1018.   },
  1019.  
  1020.   notifyOnSubscribe: function FC__notifyOnSubscribe(feed) {
  1021.     for each (var observer in this._observers) {
  1022.       observer.onSubscribe(this, feed);
  1023.     }
  1024.   },
  1025.   notifyOnUnsubscribe: function FC__notifyOnUnsubscribe(feed) {
  1026.     for each (var observer in this._observers) {
  1027.       observer.onUnsubscribe(this, feed);
  1028.     }
  1029.   },
  1030.  
  1031.   getInterfaces: function FC_getInterfaces(countRef) {
  1032.     var interfaces = [Ci.flockIFeedContext, Ci.flockIRDFObserver,
  1033.                       Ci.nsIClassInfo, Ci.nsISupports];
  1034.     countRef.value = interfaces.length;
  1035.     return interfaces;
  1036.   },
  1037.   getHelperForLanguage: function FC_getHelperForLanguage(language) {
  1038.     return null;
  1039.   },
  1040.   contractID: FC_CONTRACTID,
  1041.   classDescription: FC_CLASSNAME,
  1042.   classID: FC_CLASSID,
  1043.   implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
  1044.  
  1045.   QueryInterface: function FC_QueryInterface(iid) {
  1046.     if (iid.equals(Ci.flockIFeedContext) ||
  1047.         iid.equals(Ci.flockIRDFObserver) ||
  1048.         iid.equals(Ci.nsIClassInfo) ||
  1049.         iid.equals(Ci.nsISupports))
  1050.       return this;
  1051.     throw Cr.NS_ERROR_NO_INTERFACE;
  1052.   }
  1053. }
  1054.  
  1055.  
  1056. function FeedRequest(fm, url, coop, listener, use_feed_api, metadataOnly) {
  1057.   this._fm = fm;
  1058.   this._url = url;
  1059.   this._coop = coop;
  1060.   this._listener = listener;
  1061.   this._use_feed_api = use_feed_api;
  1062.   this._metadataOnly = metadataOnly;
  1063.  
  1064.   this._logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
  1065.   this._logger.init('feedmanager');
  1066.  
  1067.   this._channel = gIOService.newChannelFromURI(url);
  1068.  
  1069.   try {
  1070.     this._httpChannel = this._channel.QueryInterface(Ci.nsIHttpChannel);
  1071.   }
  1072.   catch (e) {
  1073.     this._httpChannel = null;
  1074.   }
  1075. }
  1076.  
  1077. FeedRequest.prototype = {
  1078.   get: function FR_get() {
  1079.     if (this._httpChannel) {
  1080.       var feedNode = getFeedNode(this._url.spec, this._coop);
  1081.       if (feedNode && feedNode.lastModification)
  1082.         this._httpChannel.setRequestHeader('If-Modified-Since',
  1083.                                            feedNode.lastModification,
  1084.                                            false);
  1085.     }
  1086.  
  1087.     this._channel.asyncOpen(this, null);
  1088.   },
  1089.  
  1090.   onStartRequest: function FR_onStartRequest(request, context) {
  1091.     var channel = request.QueryInterface(Ci.nsIChannel);
  1092.  
  1093.     if (Components.isSuccessCode(request.status))
  1094.       channel.contentType = 'text/xml';
  1095.  
  1096.     this._processor = Cc['@mozilla.org/feed-processor;1']
  1097.       .createInstance(Ci.nsIFeedProcessor);
  1098.     this._processor.listener = this;
  1099.  
  1100.     if (this._metadataOnly) {
  1101.       this._processor.QueryInterface(Ci.flockIFeedProcessor);
  1102.       this._processor.parseFeedMetadataAsync(null, channel.URI);
  1103.     } else {
  1104.       this._processor.parseAsync(null, channel.URI);
  1105.     }
  1106.  
  1107.     this._processor.onStartRequest(request, context);
  1108.   },
  1109.  
  1110.   onStopRequest: function FR_onStopRequest(request, context, status) {
  1111.     if (this._processor) {
  1112.       this._processor.onStopRequest(request, context, status);
  1113.       this._processor = null;
  1114.     }
  1115.   },
  1116.  
  1117.   onDataAvailable: function FR_onDataAvailable(request, context, inputStream,
  1118.                                                sourceOffset, count) {
  1119.     if (this._processor)
  1120.       this._processor.onDataAvailable(request, context, inputStream,
  1121.                                       sourceOffset, count);
  1122.   },
  1123.  
  1124.   handleResult: function FR_handleResult(result) {
  1125.     var failed = true;
  1126.  
  1127.     if (result && result.doc) {
  1128.       var feed = this._fm.storeFeed(this._url, result);
  1129.       if (feed) {
  1130.         failed = false;
  1131.  
  1132.         if (this._httpChannel && !this._metadataOnly)
  1133.           this._saveLastModification(feed._feedNode);
  1134.  
  1135.         this._notifyListener(feed);
  1136.       }
  1137.     } else if (this._httpChannel &&
  1138.                Components.isSuccessCode(this._httpChannel.status) &&
  1139.                this._httpChannel.responseStatus == 304) {
  1140.       this._logger.info('Feed not modified: ' + this._url.spec);
  1141.  
  1142.       var feedNode = getFeedNode(this._url.spec, this._coop);
  1143.       if (feedNode) {
  1144.         updateNextRefresh(feedNode, this._coop);
  1145.  
  1146.         if (feedNode.state != 'failed') {
  1147.           feedNode.lastFetch = new Date();
  1148.  
  1149.           var feed = new Feed(feedNode, null, this._coop);
  1150.           this._notifyListener(feed);
  1151.         }
  1152.       }
  1153.       else if (this._listener)
  1154.         this._listener.onError(null);
  1155.  
  1156.       failed = false;
  1157.     }
  1158.  
  1159.     if (failed) {
  1160.       this._logger.warn('Error processing feed: ' + this._url.spec);
  1161.  
  1162.       if (!this._metadataOnly) {
  1163.         var feedNode = getFeedNode(this._url.spec, this._coop);
  1164.         if (feedNode) {
  1165.           feedNode.state = 'failed';
  1166.           updateNextRefresh(feedNode, this._coop);
  1167.  
  1168.           if (this._httpChannel)
  1169.             this._saveLastModification(feedNode);
  1170.         }
  1171.       }
  1172.  
  1173.       if (this._listener)
  1174.         this._listener.onError(null);
  1175.     }
  1176.  
  1177.     this._processor = null;
  1178.     this._listener = null;
  1179.   },
  1180.  
  1181.   _notifyListener: function FR__notifyListener(feed) {
  1182.     if (this._listener) {
  1183.       if (this._use_feed_api)
  1184.         this._listener.onGetFeedComplete(feed);
  1185.       else
  1186.         this._listener.onResult();
  1187.     }
  1188.   },
  1189.   _saveLastModification: function FR__saveLastModification(feedNode) {
  1190.     var lastModification = null;
  1191.     try {
  1192.       lastModification = this._httpChannel.getResponseHeader('Last-Modified');
  1193.     }
  1194.     catch (e) { }
  1195.  
  1196.     if (lastModification)
  1197.       feedNode.lastModification = lastModification;
  1198.   },
  1199.  
  1200.   QueryInterface: function FR_QueryInterface(iid) {
  1201.     if (iid.equals(Ci.nsIFeedResultListener) ||
  1202.         iid.equals(Ci.nsIStreamListener) ||
  1203.         iid.equals(Ci.nsIRequestObserver)||
  1204.         iid.equals(Ci.nsISupports))
  1205.       return this;
  1206.     throw Cr.NS_ERROR_NO_INTERFACE;
  1207.   },
  1208. }
  1209.  
  1210.  
  1211. function FeedManager() {
  1212.   var obs = getObserverService();
  1213.   obs.addObserver(this, 'xpcom-shutdown', false);
  1214.  
  1215.   this._start();
  1216. }
  1217.  
  1218. FeedManager.prototype = {
  1219.   _start: function FM__start() {
  1220.     this._profiler = Cc['@flock.com/profiler;1'].getService(Ci.flockIProfiler);
  1221.     var evtID = this._profiler.profileEventStart('feedmanager-init');
  1222.  
  1223.     this._logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
  1224.     this._logger.init('feedmanager');
  1225.     this._logger.info('starting up...');
  1226.  
  1227.     this._obsService = getObserverService();
  1228.  
  1229.     gIOService = Cc['@mozilla.org/network/io-service;1']
  1230.       .getService(Ci.nsIIOService);
  1231.  
  1232.     var sbs = Cc['@mozilla.org/intl/stringbundle;1']
  1233.       .getService(Ci.nsIStringBundleService);
  1234.     var bundle = sbs.createBundle(URI_FEED_PROPERTIES);
  1235.     gReadMoreBlurb = bundle.GetStringFromName('flock.feed.temp.readmore');
  1236.  
  1237.     this._coop = Cc["@flock.com/singleton;1"]
  1238.                  .getService(Ci.flockISingleton)
  1239.                  .getSingleton("chrome://flock/content/common/load-faves-coop.js")
  1240.                  .wrappedJSObject;
  1241.  
  1242.     gFeedStorage = new FeedStorage();
  1243.  
  1244.     var feedroot = new this._coop.Folder(URN_FEED_ROOT);
  1245.  
  1246.     this._feedContexts = {};
  1247.     this.createFeedContext(NEWS_CONTEXT_NAME);
  1248.     this.createFeedContext(LIVEMARKS_CONTEXT_NAME);
  1249.  
  1250.     this._coop.FeedItem.add_destroy_notifier(this._deleteItemContent);
  1251.  
  1252.     var prefService = Cc['@mozilla.org/preferences-service;1']
  1253.       .getService(Ci.nsIPrefBranch2);
  1254.     prefService.addObserver(PREF_EXPIRATION_TIME_BRANCH, this, false);
  1255.  
  1256.     this.observe(null, 'nsPref:changed', PREF_EXPIRATION_TIME);
  1257.     this.observe(null, 'nsPref:changed', PREF_METADATA_EXPIRATION_TIME);
  1258.  
  1259.     this._profiler.profileEventEnd(evtID, '');
  1260.   },
  1261.   _shutdown: function FM__shutdown() {
  1262.     var prefService = Cc['@mozilla.org/preferences-service;1']
  1263.       .getService(Ci.nsIPrefBranch2);
  1264.     prefService.removeObserver(PREF_EXPIRATION_TIME_BRANCH, this);
  1265.  
  1266.     this._coop.FeedItem.remove_destroy_notifier(this._deleteItemContent);
  1267.  
  1268.     gIOService   = null;
  1269.     gFeedStorage = null;
  1270.  
  1271.     this._obsService = null;
  1272.   },
  1273.   _updateExpirationTimes: function FM__updateExpirationTimes(topic, pref) {
  1274.     var prefService = Cc['@mozilla.org/preferences-service;1']
  1275.       .getService(Components.interfaces.nsIPrefService);
  1276.     var prefBranch = prefService.getBranch(PREF_EXPIRATION_TIME_BRANCH);
  1277.  
  1278.     var val = 0;
  1279.     try {
  1280.       val = prefBranch.getIntPref(pref);
  1281.     }
  1282.     catch (e) { }
  1283.  
  1284.     if (pref == PREF_EXPIRATION_TIME) {
  1285.       this._refreshExpire  = val > 0 ? val : DEFAULT_EXPIRATION_TIME;
  1286.       this._refreshExpire *= 60 * 1000;
  1287.     } else {
  1288.       this._metadataExpire = val > 0 ? val : DEFAULT_METADATA_EXPIRATION_TIME;
  1289.       this._metadataExpire *= 60 * 1000;
  1290.     }
  1291.   },
  1292.  
  1293.   observe: function FM_observe(subject, topic, state) {
  1294.     var obs = getObserverService();
  1295.  
  1296.     switch (topic) {
  1297.       case 'xpcom-shutdown':
  1298.         obs.removeObserver(this, 'xpcom-shutdown');
  1299.         this._shutdown();
  1300.         break;
  1301.  
  1302.       case 'nsPref:changed':
  1303.         this._updateExpirationTimes(topic, state);
  1304.         break;
  1305.     }
  1306.   },
  1307.  
  1308.   getFeed: function FM_getFeed(url, listener) {
  1309.     var feedNode = getFeedNode(url.spec, this._coop);
  1310.     if (feedNode && !feedNode.metadataOnly) {
  1311.       var age = Date.now() - feedNode.lastFetch.getTime();
  1312.       if (age < this._refreshExpire) {
  1313.         this._logger.info("getting feed from cache: " + url.spec);
  1314.         var feed = new Feed(feedNode, null, this._coop);
  1315.         if (listener)
  1316.           listener.onGetFeedComplete(feed);
  1317.         return;
  1318.       }
  1319.     }
  1320.  
  1321.     this.getFeedBypassCache(url, listener);
  1322.   },
  1323.  
  1324.   getFeedMetadata: function FM_getFeedMetadata(url, listener) {
  1325.     var feedNode = getFeedNode(url.spec, this._coop);
  1326.     if (feedNode) {
  1327.       if (!feedNode.metadataOnly) {
  1328.         this._logger.info("getting feed metadata from complete feed: " +
  1329.                           url.spec);
  1330.         this.getFeed(url, listener);
  1331.         return;
  1332.       } else {
  1333.         var age = Date.now() - feedNode.lastFetch.getTime();
  1334.         if (age < this._metadataExpire) {
  1335.           this._logger.info("getting feed metadata from cache: " + url.spec);
  1336.           var feed = new Feed(feedNode, null, this._coop);
  1337.           if (listener)
  1338.             listener.onGetFeedComplete(feed);
  1339.           return;
  1340.         }
  1341.       }
  1342.     }
  1343.  
  1344.     this._logger.info("getting feed metadata from network: " + url.spec);
  1345.     var req = new FeedRequest(this, url, this._coop, listener, true, true);
  1346.     req.get();
  1347.   },
  1348.  
  1349.   getFeedBypassCache: function FM_getFeedBypassCache(url, listener) {
  1350.     this._logger.info("getting feed from network: " + url.spec);
  1351.     var req = new FeedRequest(this, url, this._coop, listener, true, false);
  1352.     req.get();
  1353.   },
  1354.  
  1355.   createFeedContext: function FM_createFeedContext(name) {
  1356.     var ctxt = this._feedContexts[name];
  1357.     if (ctxt)
  1358.       return ctxt;
  1359.  
  1360.     var context = new this._coop.FeedContext({ name: name });
  1361.     context.flaggedItems = new this._coop.FeedFlaggedStream({ context: context });
  1362.  
  1363.     var feedroot = this._coop.get(URN_FEED_ROOT);
  1364.     feedroot.children.addOnce(context);
  1365.  
  1366.     ctxt = new FeedContext(context, this._coop);
  1367.     this._feedContexts[name] = ctxt;
  1368.  
  1369.     return ctxt;
  1370.   },
  1371.  
  1372.   getFeedContext: function FM_getFeedContext(name) {
  1373.     var ctxt = this._feedContexts[name];
  1374.     if (ctxt)
  1375.       return ctxt;
  1376.  
  1377.     var urn = this._coop.FeedContext.get_id({ name: name });
  1378.     var context = this._coop.get(urn);
  1379.     if (context) {
  1380.       ctxt = new FeedContext(context, this._coop);
  1381.       this._feedContexts[name] = ctxt;
  1382.       return ctxt;
  1383.     } else {
  1384.       throw Components.Exception("No feed context named " + name);
  1385.     }
  1386.   },
  1387.  
  1388.   getFeedContexts: function FM_getFeedContexts() {
  1389.     var contexts = this._coop.FeedContext.all();
  1390.     var fm = this;
  1391.     var ctor = function(contextNode, coop) {
  1392.       var ctxt = fm._feedContexts[contextNode.name];
  1393.       if (!ctxt) {
  1394.         ctxt = new FeedContext(contextNode, coop);
  1395.         fm.feedContexts[contextNode.name] = ctxt;
  1396.       }
  1397.       return ctxt;
  1398.     }
  1399.     return this._enumerateObjects(contexts, ctor);
  1400.   },
  1401.  
  1402.   deleteFeedContext: function FM_deleteFeedContext(name) {
  1403.     var urn = this._coop.FeedContext.get_id({ name: name });
  1404.     var context = this._coop.get(urn);
  1405.     if (!context)
  1406.       throw Components.Exception("No feed context named " + name);
  1407.     
  1408.     delete this._feedContexts[name];
  1409.  
  1410.     context.flaggedItems.destroy();
  1411.     // FIXME: delete folders as well
  1412.     context.destroy();
  1413.   },
  1414.  
  1415.   existsFeed: function FM_existsFeed(url) {
  1416.     var urn = this._coop.Feed.get_id({ URL: url.spec });
  1417.     return this._coop.Feed.exists(urn);
  1418.   },
  1419.  
  1420.   refresh: function FM_refresh(urn, listener) {
  1421.     this._logger.info('refreshing feed: ' + urn);
  1422.     var feedNode = this._coop.get(urn);
  1423.     if (feedNode) {
  1424. /*
  1425.       if (!feedNode.metadataOnly) {
  1426.         var age = Date.now() - feedNode.lastFetch.getTime();
  1427.         if (age < this._refreshExpire) {
  1428.           if (listener)
  1429.             listener.onResult();
  1430.           return;
  1431.         }
  1432.       }
  1433. */
  1434.  
  1435.       var req = new FeedRequest(this, makeURI(feedNode.URL), this._coop,
  1436.                                 listener, false, false);
  1437.       req.get();
  1438.     } else {
  1439.       var msg = 'could not get feed: ' + urn;
  1440.       this._logger.error(msg);
  1441.       throw Components.Exception(msg, Cr.NS_ERROR_UNEXPECTED);
  1442.     }
  1443.   },
  1444.  
  1445.   getFeedFolderItem: function FM_getFeedFolderItem(urn) {
  1446.     this._logger.info('getting feed folder item: ' + urn);
  1447.     var node = this._coop.get(urn);
  1448.     if (node) {
  1449.       var context = this.getFeedContext(NEWS_CONTEXT_NAME);
  1450.       if (node.isInstanceOf(this._coop.Feed)) {
  1451.         return new Feed(node, context, this._coop);
  1452.       } else if (node.isInstanceOf(this._coop.FeedFolder)) {
  1453.         return new FeedFolder(node, context, this._coop);
  1454.       }
  1455.     }
  1456.  
  1457.     this._logger.warn('could not get feed folder item: ' + urn);
  1458.     return null;
  1459.   },
  1460.   getFeedItem: function FM_getFeedItem(urn) {
  1461.     this._logger.info('getting feed item: ' + urn);
  1462.     var node = this._coop.get(urn);
  1463.     if (node && node.isInstanceOf(this._coop.FeedItem)) {
  1464.       var parents = node.getParents();
  1465.       for each (var parent in parents) {
  1466.         if (parent.isInstanceOf(this._coop.Feed)) {
  1467.           var context = this.getFeedContext(NEWS_CONTEXT_NAME);
  1468.           var feed = new Feed(parent, context, this._coop);
  1469.           return new FeedItem(feed, node, this._coop);
  1470.         }
  1471.       }
  1472.     }
  1473.  
  1474.     this._logger.warn('could not get feed item: ' + urn);
  1475.     return null;
  1476.   },
  1477.  
  1478.   getLibrary: function FM_getLibrary() {
  1479.     var feeds = this._coop.Feed.all();
  1480.     var ctor = function(feedNode, coop) {
  1481.       return new Feed(feedNode, null, coop);
  1482.     }
  1483.     return this._enumerateObjects(feed, ctor);
  1484.   },
  1485.  
  1486.   storeFeed: function FM_storeFeed(feedURL, feedResult) {
  1487.     if (feedResult.bozo)
  1488.       return null;
  1489.  
  1490.     var feed = feedResult.doc;
  1491.     feed.QueryInterface(Ci.nsIFeed);
  1492.  
  1493.     var flockFeed = feed.QueryInterface(Ci.flockIFeedContainer);
  1494.     if (flockFeed.metadataOnly) {
  1495.       var evtID = this._profiler.profileEventStart('feedmanager-store-feed-metadata');
  1496.       this._logger.debug('storing feed metadata: ' + feedURL.spec);
  1497.     } else {
  1498.       var evtID = this._profiler.profileEventStart('feedmanager-store-feed');
  1499.       this._logger.info('storing feed: ' + feedURL.spec);
  1500.     }
  1501.  
  1502.     var title    = feed.title    ? feed.title.plainText()    : null;
  1503.     var subtitle = feed.subtitle ? feed.subtitle.plainText() : null;
  1504.  
  1505.     var format = feedResult.version ? feedResult.version : UNKNOWN_FEED_TYPE;
  1506.  
  1507.     var author = null;
  1508.     if (feed.authors && feed.authors.length)
  1509.       author = feed.authors.queryElementAt(0, Ci.nsIFeedPerson).name;
  1510.  
  1511.     var image = null;
  1512.     try {
  1513.       image = makeURI(feed.image.getPropertyAsAString('url'));
  1514.     }
  1515.     catch (e) { }
  1516.  
  1517.     var existingFeed = getFeedNode(feedURL.spec, this._coop);
  1518.     if (existingFeed)
  1519.       title = existingFeed.name;
  1520.  
  1521.     var feedNode = this._newFeedNode(feedURL, feedResult.uri,
  1522.                                      title, subtitle, feed.link, author,
  1523.                                      format, image, flockFeed.metadataOnly);
  1524.  
  1525.     feedNode.lastFetch = new Date();
  1526.  
  1527.     if (flockFeed.metadataOnly) {
  1528.       this._profiler.profileEventEnd(evtID, feedURL.spec);
  1529.       return new Feed(feedNode, null, this._coop);
  1530.     }
  1531.  
  1532.     updateNextRefresh(feedNode, this._coop);
  1533.  
  1534.     insertFavicon(feedNode);
  1535.  
  1536.     var feedItems = feed.items;
  1537.     var numItems = feedItems.length;
  1538.  
  1539.     var items = [];
  1540.  
  1541.     for (var i = 0; i < numItems; i++) {
  1542.       var item = feedItems.queryElementAt(i, Ci.nsIFeedEntry);
  1543.       var datevalue = item.updated ? new Date(item.updated)
  1544.                                    : feedNode.datevalue;
  1545.       items.push({item: item, datevalue: datevalue, indexvalue: i});
  1546.     }
  1547.  
  1548.     items.sort(feedItemSorter);
  1549.  
  1550.     var refDate;
  1551.     var children = feedNode.children.enumerateBackwards();
  1552.     if (children.hasMoreElements())
  1553.       refDate = children.getNext().datevalue;
  1554.     else
  1555.       refDate = new Date(0);
  1556.  
  1557.     gFeedStorage.beginTransaction();
  1558.  
  1559.     try {
  1560.       var itemsAdded = 0;
  1561.       for each (var i in items) {
  1562.         var node = this._storeFeedEntry(i.item, feedNode, refDate, feed);
  1563.         if (node)
  1564.           itemsAdded++;
  1565.       }
  1566.     }
  1567.     finally {
  1568.       gFeedStorage.commitTransaction();
  1569.     }
  1570.  
  1571.     children = feedNode.children.enumerateBackwards();
  1572.     if (children.hasMoreElements())
  1573.       feedNode.datevalue = children.getNext().datevalue;
  1574.     else
  1575.       feedNode.datevalue = new Date();
  1576.  
  1577.     var feedObj = new Feed(feedNode, null, this._coop);
  1578.  
  1579.     if (itemsAdded && existingFeed)
  1580.       this._obsService.notifyObservers(feedObj, 'new-feed-items', null);
  1581.  
  1582.     this._profiler.profileEventEnd(evtID, feedURL.spec);
  1583.  
  1584.     return feedObj;
  1585.   },
  1586.  
  1587.   _newFeedNode: function FM__newFeedNode(feedURL, finalURL, title, subtitle,
  1588.                                          link, author, format, image,
  1589.                                          metadataOnly) {
  1590.     var feedInfo = { URL: feedURL.spec,
  1591.                      finalURL: finalURL ? finalURL.spec : feedURL.spec,
  1592.                      name: title ? title : feedURL.spec,
  1593.                      subtitle: subtitle,
  1594.                      link: link ? link.spec : feedURL.spec,
  1595.                      author: author ? author : null,
  1596.                      format: format ? format : UNKNOWN_FEED_TYPE,
  1597.                      image: image ? image.spec : null,
  1598.                      metadataOnly: metadataOnly,
  1599.                      state: 'ok',
  1600.                      serviceId: FM_CONTRACTID
  1601.                    };
  1602.     return new this._coop.Feed(feedInfo);
  1603.   },
  1604.  
  1605.   _storeFeedEntry: function FM__storeFeedEntry(item, feedNode, refDate, feed) {
  1606.     var title = null;
  1607.     if (item.title)
  1608.       title = item.title.plainText();
  1609.  
  1610.     var author = null;
  1611.     if (item.authors && item.authors.length)
  1612.       author = item.authors.queryElementAt(0, Ci.nsIFeedPerson).name;
  1613.  
  1614.     var pubdate = item.published;
  1615.     if (!pubdate)
  1616.       pubdate = item.updated;
  1617.     if (!pubdate)
  1618.       pubdate = feed.updated;
  1619.  
  1620.     var content = null;
  1621.     if (item.content)
  1622.       content = item.content;
  1623.     else if (item.summary)
  1624.       content = item.summary;
  1625.  
  1626.     var excerptSource = null;
  1627.     if (item.summary)
  1628.        excerptSource = item.summary.plainText();
  1629.     else if (item.content)
  1630.        excerptSource = item.content.plainText();
  1631.  
  1632.     return this._storeFeedItem(feedNode, item.id, title,
  1633.                                item.link, author, pubdate, refDate,
  1634.                                content, excerptSource,
  1635.                                false, false);
  1636.   },
  1637.  
  1638.   _storeFeedItem: function FM__storeFeedItem(feedNode, id, title, link, author,
  1639.                                              pubdate, refDate,
  1640.                                              content, excerptSource,
  1641.                                              read, flagged) {
  1642.     if (!id && link)
  1643.       id = link.spec;
  1644.  
  1645.     if (!link)
  1646.       link = makeURI(feedNode.URL);
  1647.  
  1648.     if (!title && excerptSource)
  1649.       title = excerptSource.substr(0, TITLE_TRIM_MAX_CHARS) + '...';
  1650.  
  1651.     if (title)
  1652.       title = title.replace(/[\r\n]+/g, ' ');
  1653.     else
  1654.       title = link.spec;
  1655.  
  1656.     if (pubdate)
  1657.        pubdate = new Date(pubdate);
  1658.  
  1659.     var itemInfo = { itemid: id,
  1660.                      URL: link.spec,
  1661.                      name: title,
  1662.                      author: author,
  1663.                      unseen: !read,
  1664.                      flagged: flagged,
  1665.                      parentfeed: feedNode.URL
  1666.                    };
  1667.  
  1668.     //TODO: we'll want to be more intelligent about this, but the goal should be to only update feed items that need updating
  1669.     var rdfId = this._coop.FeedItem.get_id(itemInfo);
  1670.     if (rdfId && this._coop.FeedItem.exists(rdfId))
  1671.       return null;
  1672.  
  1673.     itemInfo.pubdate = pubdate;
  1674.  
  1675.     if (!pubdate)
  1676.       itemInfo.datevalue = new Date();
  1677.     else if (pubdate.getTime() > Date.now())
  1678.       itemInfo.datevalue = new Date();
  1679.     else if (pubdate < refDate)
  1680.       itemInfo.datevalue = new Date();
  1681.     else
  1682.       itemInfo.datevalue = pubdate;
  1683.  
  1684.     itemInfo.isIndexable = feedNode.isIndexable;
  1685.  
  1686.     this._logger.info('storing feeditem: ' + itemInfo.name);
  1687.  
  1688.     var itemNode = new this._coop.FeedItem(itemInfo);
  1689.  
  1690.     if (feedNode.children.indexOf(itemNode) < 0)
  1691.       feedNode.addItem(itemNode);
  1692.  
  1693.     if (content) {
  1694.       if (typeof(content) != 'string') {
  1695.         var parser = Cc['@mozilla.org/xmlextras/domparser;1']
  1696.           .createInstance(Ci.nsIDOMParser);
  1697.         var doc = parser.parseFromString('<div/>', 'application/xhtml+xml');
  1698.  
  1699.         var docElem = doc.documentElement;
  1700.         var fragment = content.createDocumentFragment(docElem);
  1701.         docElem.appendChild(fragment);
  1702.  
  1703.         this._filterFeedItemContent(doc, content.base);
  1704.  
  1705.         var serializer = Cc['@mozilla.org/xmlextras/xmlserializer;1']
  1706.           .createInstance(Ci.nsIDOMSerializer);
  1707.         var data = serializer.serializeToString(doc);
  1708.       } else {
  1709.         data = content;
  1710.       }
  1711.  
  1712.       var excerpt = excerptText(excerptSource, itemInfo.URL);
  1713.       gFeedStorage.saveItem(itemNode.id(), feedNode.URL, data, excerpt);
  1714.     } else {
  1715.       gFeedStorage.deleteItem(itemNode.id());
  1716.     }
  1717.  
  1718.     return itemNode;
  1719.   },
  1720.   _filterFeedItemContent: function FM__filterFeedItemContent(doc, baseURI) { 
  1721.     if (!baseURI)
  1722.       return;
  1723.  
  1724.     var tw = doc.createTreeWalker(doc.documentElement,
  1725.                                   Ci.nsIDOMNodeFilter.SHOW_ELEMENT,
  1726.                                   null, false);
  1727.     node = tw.nextNode();
  1728.     while (node) {
  1729.       for each (var attrName in ATTR_URI_LIST) {
  1730.         var attr = node.attributes.getNamedItem(attrName);
  1731.         if (attr) {
  1732.           var nval = attr.nodeValue;
  1733.           try {
  1734.             var newURI = gIOService.newURI(nval, null, baseURI);
  1735.             attr.nodeValue = newURI.spec;
  1736.           }
  1737.           catch (e) { }
  1738.         }
  1739.       }
  1740.       node = tw.nextNode();
  1741.     }
  1742.   },
  1743.  
  1744.   _deleteItemContent: function FM__deleteItemContent(item, coop) {
  1745.     gFeedStorage.deleteItem(item.id());
  1746.   },
  1747.  
  1748.   _purgeFeed: function FM__purgeFeed(feed) {
  1749.     this._logger.info('Purging ' + feed.id());
  1750.  
  1751.     var items = feed.children.enumerateBackwards();
  1752.     if (items.hasMoreElements()) {
  1753.       this._coop.FeedItem.remove_destroy_notifier(this._deleteItemContent);
  1754.  
  1755.       gFeedStorage.deleteFeed(feed.URL);
  1756.  
  1757.       while (items.hasMoreElements())
  1758.         items.getNext().destroy();
  1759.  
  1760.       this._coop.FeedItem.add_destroy_notifier(this._deleteItemContent);
  1761.     }
  1762.  
  1763.     feed.destroy();
  1764.   },
  1765.  
  1766.   _enumerateObjects: function FM__enumerateObjects(objects, ctor) {
  1767.     var coop = this._coop;
  1768.  
  1769.     var enumerator = {
  1770.       hasMoreElements: function() {
  1771.         return objects.hasMoreElements();
  1772.       },
  1773.       getNext: function() {
  1774.         return ctor(objects.getNext(), coop);
  1775.       }
  1776.     }
  1777.  
  1778.     return enumerator;
  1779.   },
  1780.  
  1781.   _purgeOrphanedFeeds: function FM__purgeOrphanedFeeds() {
  1782.     var feeds = this._coop.Feed.all();
  1783.  
  1784.     var fm = this;
  1785.  
  1786.     var purge = {
  1787.       notify: function(timer) {
  1788.         var start = Date.now();
  1789.         while (feeds.hasMoreElements()) {
  1790.           var feed = feeds.getNext();
  1791.  
  1792.           var contexts = getContextsForFeed(feed, fm._coop);
  1793.           if (contexts.length == 0)
  1794.             fm._purgeFeed(feed);
  1795.  
  1796.           if (Date.now() > start + PURGE_RUN_INTERVAL)
  1797.             return;
  1798.         }
  1799.  
  1800.         timer.cancel();
  1801.       }
  1802.     };
  1803.  
  1804.     var timer = Cc['@mozilla.org/timer;1'].createInstance(Ci.nsITimer);
  1805.     timer.initWithCallback(purge, PURGE_SLEEP_INTERVAL,
  1806.                            Ci.nsITimer.TYPE_REPEATING_SLACK);
  1807.  
  1808.   },
  1809.  
  1810.   /* flockIHousekeeping interface */
  1811.   runHousekeeping: function FM_runHousekeeping() {
  1812.     this._logger.info('running housekeeping...');
  1813.     this._purgeOrphanedFeeds();
  1814.   },
  1815.  
  1816.   /* flockIMigratable interface */
  1817.   get migrationName() { return "News Feeds"; },
  1818.  
  1819.   needsMigration: function FM_needsMigration(oldVersion) {
  1820.     var oldFeedsFile = this._getOldFeedServiceFile(OLD_FEEDS_RDF_FILE);
  1821.     return oldFeedsFile.exists();
  1822.   },
  1823.   startMigration: function FM_startMigration(oldVersion, listener) {
  1824.     var obs = getObserverService();
  1825.     obs.notifyObservers(null, 'myworld-enableDisable-observers', false);
  1826.     
  1827.     this._start();
  1828.  
  1829.     gFeedStorage.beginTransaction();
  1830.  
  1831.     var ctxt = {
  1832.       listener: listener,
  1833.  
  1834.       oldFeedsFile     : this._getOldFeedServiceFile(OLD_FEEDS_RDF_FILE),
  1835.       oldFlaggedFile   : this._getOldFeedServiceFile(OLD_FLAGGED_RDF_FILE),
  1836.       oldRootFile      : this._getOldFeedServiceFile(OLD_ROOT_RDF_FILE),
  1837.       oldDiscoveryFile : this._getOldFeedServiceFile(OLD_DISCOVERY_RDF_FILE),
  1838.       oldFeedDir       : this._getOldFeedServiceFile(OLD_FEED_DATA_DIR),
  1839.  
  1840.       oldFeedsReported   : false,
  1841.       oldFlaggedReported : false,
  1842.       cleanupReported    : false,
  1843.     };
  1844.  
  1845.     return { wrappedJSObject: ctxt };
  1846.   },
  1847.   finishMigration: function FM_finishMigration(ctxtWrapper) {
  1848.     gFeedStorage.commitTransaction();
  1849.     
  1850.     var obs = getObserverService();
  1851.     obs.notifyObservers(null, 'myworld-enableDisable-observers', true);
  1852.   },
  1853.   doMigrationWork: function FM_doMigrationWork(ctxtWrapper) {
  1854.     var ctxt = ctxtWrapper.wrappedJSObject;
  1855.  
  1856.     if (!ctxt.oldFeedsReported) {
  1857.       ctxt.listener.onUpdate(0, 'Migrating subscriptions');
  1858.       ctxt.oldFeedsReported = true;
  1859.     } else if (ctxt.oldFeedsFile.exists()) {
  1860.       if (!ctxt.oldFeedsMigrator)
  1861.         ctxt.oldFeedsMigrator = this._migrateOldFeeds(ctxt);
  1862.       if (ctxt.oldFeedsMigrator.next())
  1863.         ctxt.oldFeedsMigrator = null;
  1864.     } else if (!ctxt.oldFlaggedReported) {
  1865.       ctxt.listener.onUpdate(-1, 'Migrating saved articles');
  1866.       ctxt.oldFlaggedReported = true;
  1867.     } else if (ctxt.oldFlaggedFile.exists()) {
  1868.       this._migrateOldFlagged(ctxt);
  1869.     } else if (!ctxt.cleanupReported) {
  1870.       ctxt.listener.onUpdate(-1, 'Cleaning up old data');
  1871.       ctxt.cleanupReported = true;
  1872.     } else if (ctxt.oldRootFile.exists()) {
  1873.       ctxt.oldRootFile.remove(false);
  1874.     } else if (ctxt.oldDiscoveryFile.exists()) {
  1875.       ctxt.oldDiscoveryFile.remove(false);
  1876.     } else if (ctxt.oldFeedDir.exists()) {
  1877.       ctxt.oldFeedDir.remove(true);
  1878.       return false;
  1879.     } else {
  1880.       return false;
  1881.     }
  1882.  
  1883.     return true;
  1884.   },
  1885.  
  1886.   _getOldFeedServiceFile: function FM__getOldFeedServiceFile(filename) {
  1887.     var oldFile = Cc['@mozilla.org/file/directory_service;1']
  1888.       .getService(Ci.nsIProperties).get("ProfD", Ci.nsIFile);
  1889.     oldFile.append(filename)
  1890.     return oldFile;
  1891.   },
  1892.   _migrateOldFeeds: function FM__migrateOldFeeds(ctxt) {
  1893.     var RDFS = Cc['@mozilla.org/rdf/rdf-service;1']
  1894.       .getService(Ci.nsIRDFService);
  1895.     var RDFCU = Cc['@mozilla.org/rdf/container-utils;1']
  1896.       .getService(Ci.nsIRDFContainerUtils);
  1897.  
  1898.     var spec = gIOService.newFileURI(ctxt.oldFeedsFile).spec;
  1899.     var ds = RDFS.GetDataSourceBlocking(spec);
  1900.     var root = RDFS.GetResource(SUBSCRIPTIONS_RDF_ROOT);
  1901.  
  1902.     var context = this.getFeedContext(NEWS_CONTEXT_NAME);
  1903.     var news_root = context.getRoot();
  1904.  
  1905.     var titleProp = RDFS.GetResource(FLOCK_NS + 'title');
  1906.  
  1907.     var typeProp = RDFS.GetResource(FLOCK_NS + 'type');
  1908.     var feeds = ds.GetSources(typeProp, RDFS.GetLiteral('feed'), true);
  1909.  
  1910.     var numFeeds = 0;
  1911.     while (feeds && feeds.hasMoreElements()) {
  1912.       var feed = feeds.getNext();
  1913.       numFeeds++;
  1914.     }
  1915.  
  1916.     var children = RDFCU.MakeSeq(ds, root).GetElements(), i = 0;
  1917.     while (children && children.hasMoreElements()) {
  1918.       var child = children.getNext();
  1919.       child.QueryInterface(Ci.nsIRDFResource);
  1920.  
  1921.       if (RDFCU.IsContainer(ds, child)) {
  1922.         var title = ds.GetTarget(child, titleProp, true);
  1923.         if (!title)
  1924.           continue;
  1925.  
  1926.         title.QueryInterface(Ci.nsIRDFLiteral);
  1927.         if (!title.Value)
  1928.           continue;
  1929.  
  1930.         var subFolder = news_root.addFolder(title.Value);
  1931.  
  1932.         var subChildren = RDFCU.MakeSeq(ds, child).GetElements();
  1933.         while (subChildren && subChildren.hasMoreElements()) {
  1934.           var subChild = subChildren.getNext();
  1935.           subChild.QueryInterface(Ci.nsIRDFResource);
  1936.  
  1937.           var url = subChild.Value;
  1938.           var percent = Math.round(i / numFeeds * 100);
  1939.           ctxt.listener.onUpdate(percent, 'Migrating ' + url);
  1940.           yield false;
  1941.  
  1942.           i++;
  1943.           this._migrateFeed(url, subFolder);
  1944.         }
  1945.       } else {
  1946.         var url = child.Value;
  1947.         var percent = Math.round(i / numFeeds * 100);
  1948.         ctxt.listener.onUpdate(percent, 'Migrating ' + url);
  1949.         yield false;
  1950.  
  1951.         i++;
  1952.         this._migrateFeed(url, news_root);
  1953.       }
  1954.     }
  1955.  
  1956.     var oldFile = ctxt.oldFeedsFile.clone();
  1957.     oldFile.moveTo(null, OLD_FEEDS_RDF_FILE_RELIC);
  1958.     //ctxt.oldFeedsFile.remove(false);
  1959.  
  1960.     yield true;
  1961.   },
  1962.   _migrateFeed: function FM__migrateFeed(url, folder) {
  1963.     var RDFS = Cc['@mozilla.org/rdf/rdf-service;1']
  1964.       .getService(Ci.nsIRDFService);
  1965.     var RDFCU = Cc['@mozilla.org/rdf/container-utils;1']
  1966.       .getService(Ci.nsIRDFContainerUtils);
  1967.  
  1968.     var oldFeedFile = this._getOldFeedServiceFile(OLD_FEED_DATA_DIR);
  1969.     oldFeedFile.append(FlockCryptoHash.md5(url));
  1970.     oldFeedFile.append(OLD_FEED_DATA_FILENAME);
  1971.  
  1972.     if (!oldFeedFile.exists())
  1973.       return;
  1974.  
  1975.     var spec = gIOService.newFileURI(oldFeedFile).spec;
  1976.     var ds = RDFS.GetDataSourceBlocking(spec);
  1977.     var root = RDFS.GetResource(OLD_FEED_DATA_RDF_PREFIX + url);
  1978.  
  1979.     function getTarget(prop) {
  1980.       var t = ds.GetTarget(root, prop, true);
  1981.       return t ? t.QueryInterface(Ci.nsIRDFLiteral).Value : null;
  1982.     }
  1983.  
  1984.     var title    = getTarget(RDFS.GetResource(FLOCK_NS + 'title'));
  1985.     var subtitle = getTarget(RDFS.GetResource(FLOCK_NS + 'description'));
  1986.     var author   = getTarget(RDFS.GetResource(FLOCK_NS + 'author'));
  1987.     var link     = getTarget(RDFS.GetResource(FLOCK_NS + 'link'));
  1988.     var format   = getTarget(RDFS.GetResource(FLOCK_NS + 'feedtype'));
  1989.     var image    = getTarget(RDFS.GetResource(FLOCK_NS + 'image'));
  1990.     var favicon  = getTarget(RDFS.GetResource(FLOCK_NS + 'favicon'));
  1991.  
  1992.     if (FORMAT_CONVERSIONS[format])
  1993.       format = FORMAT_CONVERSIONS[format];
  1994.     else if (format.indexOf('RSS') == 0)
  1995.       format = 'rssUnknown';
  1996.     else
  1997.       format = null;
  1998.  
  1999.     var feedURL = makeURI(url);
  2000.     if (!feedURL)
  2001.       return;
  2002.  
  2003.     var feedNode = this._newFeedNode(feedURL, null, title, subtitle,
  2004.                                      makeURI(link), author, format,
  2005.                                      makeURI(image), false);
  2006.  
  2007.     var faviconURL = makeURI(favicon);
  2008.     if (faviconURL)
  2009.       feedNode.favicon = faviconURL.spec;
  2010.  
  2011.     folder.subscribeFeed(new Feed(feedNode, null, this._coop));
  2012.  
  2013.     var items = [];
  2014.  
  2015.     var children = RDFCU.MakeSeq(ds, root).GetElements();
  2016.     while (children && children.hasMoreElements()) {
  2017.       var item = this._migrateFeedItem(ds, children.getNext(), false);
  2018.       if (item)
  2019.         items.push(item);
  2020.     }
  2021.  
  2022.     items.sort(feedItemSorter);
  2023.  
  2024.     for each (var i in items) {
  2025.       var args = i.item;
  2026.       args.unshift(feedNode);
  2027.       this._storeFeedItem.apply(this, args);
  2028.     }
  2029.  
  2030.     children = feedNode.children.enumerateBackwards();
  2031.     if (children.hasMoreElements())
  2032.       feedNode.datevalue = children.getNext().datevalue;
  2033.     else
  2034.       feedNode.datevalue = new Date();
  2035.   },
  2036.   _migrateOldFlagged: function FM__migrateOldFlagged(ctxt) {
  2037.     var RDFS = Cc['@mozilla.org/rdf/rdf-service;1']
  2038.       .getService(Ci.nsIRDFService);
  2039.     var RDFCU = Cc['@mozilla.org/rdf/container-utils;1']
  2040.       .getService(Ci.nsIRDFContainerUtils);
  2041.  
  2042.     var spec = gIOService.newFileURI(ctxt.oldFlaggedFile).spec;
  2043.     var ds = RDFS.GetDataSourceBlocking(spec);
  2044.     var root = RDFS.GetResource(FLAGGED_RDF_ROOT);
  2045.  
  2046.     var context = new this._coop.FeedContext({ name: NEWS_CONTEXT_NAME });
  2047.     var flaggedItems = context.flaggedItems;
  2048.  
  2049.     var feedProp = RDFS.GetResource(FLOCK_NS + 'feed');
  2050.  
  2051.     var children = RDFCU.MakeSeq(ds, root).GetElements();
  2052.     while (children && children.hasMoreElements()) {
  2053.       var child = children.getNext();
  2054.       child.QueryInterface(Ci.nsIRDFResource);
  2055.  
  2056.       var feedURL = ds.GetTarget(child, feedProp, true);
  2057.       if (!feedURL)
  2058.         continue;
  2059.  
  2060.       feedURL.QueryInterface(Ci.nsIRDFLiteral);
  2061.  
  2062.       var feedNode = getFeedNode(feedURL.Value, this._coop);
  2063.       if (feedNode) {
  2064.         var args = this._migrateFeedItem(ds, child, true).item;
  2065.         args.unshift(feedNode);
  2066.         var node = this._storeFeedItem.apply(this, args);
  2067.         if (node)
  2068.           flaggedItems.addItem(node);
  2069.       }
  2070.     }
  2071.  
  2072.     var oldFile = ctxt.oldFlaggedFile.clone();
  2073.     oldFile.moveTo(null, OLD_FLAGGED_RDF_FILE_RELIC);
  2074.     //ctxt.oldFlaggedFile.remove(false);
  2075.   },
  2076.   _migrateFeedItem: function FM__migrateFeedItem(ds, res, doFlagged) {
  2077.     var RDFS = Cc['@mozilla.org/rdf/rdf-service;1']
  2078.       .getService(Ci.nsIRDFService);
  2079.  
  2080.     res.QueryInterface(Ci.nsIRDFResource);
  2081.  
  2082.     function getTarget(prop) {
  2083.       var t = ds.GetTarget(res, prop, true);
  2084.       return t ? t.QueryInterface(Ci.nsIRDFLiteral).Value : null;
  2085.     }
  2086.  
  2087.     var flagged = getTarget(RDFS.GetResource(FLOCK_NS + 'flagged'));
  2088.     if (flagged == 'true' && !doFlagged)
  2089.       return;
  2090.  
  2091.     var id = res.Value;
  2092.     if (id.indexOf(OLD_FEED_DATA_POST_PREFIX) == 0)
  2093.       id = id.substr(OLD_FEED_DATA_POST_PREFIX.length);
  2094.     else
  2095.       id = null;
  2096.  
  2097.     var title      = getTarget(RDFS.GetResource(FLOCK_NS + 'title'));
  2098.     var link       = getTarget(RDFS.GetResource(FLOCK_NS + 'link'));
  2099.     var datevalue  = getTarget(RDFS.GetResource(FLOCK_NS + 'datevalue'));
  2100.     var author     = getTarget(RDFS.GetResource(FLOCK_NS + 'author'));
  2101.     var content    = getTarget(RDFS.GetResource(FLOCK_NS + 'description'));
  2102.     var read       = getTarget(RDFS.GetResource(FLOCK_NS + 'read'));
  2103.  
  2104.     datevalue = new Date(Number(datevalue));
  2105.     refDate = new Date(0);
  2106.  
  2107.     var item = [id, title, makeURI(link), author, datevalue, refDate, content,
  2108.                 content, read == 'true', doFlagged];
  2109.  
  2110.     return ({ item: item, datevalue: datevalue, indexvalue: 0 });
  2111.   },
  2112.  
  2113.   getInterfaces: function FM_getInterfaces(countRef) {
  2114.     var interfaces = [Ci.flockIFeedManager, Ci.flockIPollingService,
  2115.                       Ci.flockIMigratable, Ci.flockIHousekeeping,
  2116.                       Ci.nsIObserver, Ci.nsIClassInfo, Ci.nsISupports];
  2117.     countRef.value = interfaces.length;
  2118.     return interfaces;
  2119.   },
  2120.   getHelperForLanguage: function FM_getHelperForLanguage(language) {
  2121.     return null;
  2122.   },
  2123.   contractID: FM_CONTRACTID,
  2124.   classDescription: FM_CLASSNAME,
  2125.   classID: FM_CLASSID,
  2126.   implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
  2127.   flags: Ci.nsIClassInfo.SINGLETON,
  2128.  
  2129.   QueryInterface: function FM_QueryInterface(iid) {
  2130.     if (iid.equals(Ci.flockIFeedManager) ||
  2131.         iid.equals(Ci.flockIPollingService) ||
  2132.         iid.equals(Ci.flockIMigratable) ||
  2133.         iid.equals(Ci.flockIHousekeeping) ||
  2134.         iid.equals(Ci.nsIObserver) ||
  2135.         iid.equals(Ci.nsIClassInfo) ||
  2136.         iid.equals(Ci.nsISupports))
  2137.       return this;
  2138.     throw Cr.NS_ERROR_NO_INTERFACE;
  2139.   }
  2140. }
  2141.  
  2142.  
  2143. function GenericComponentFactory(ctor) {
  2144.   this._ctor = ctor;
  2145. }
  2146.  
  2147. GenericComponentFactory.prototype = {
  2148.  
  2149.   _ctor: null,
  2150.  
  2151.   // nsIFactory
  2152.   createInstance: function(outer, iid) {
  2153.     if (outer != null)
  2154.       throw Cr.NS_ERROR_NO_AGGREGATION;
  2155.     return (new this._ctor()).QueryInterface(iid);
  2156.   },
  2157.  
  2158.   // nsISupports
  2159.   QueryInterface: function(iid) {
  2160.     if (iid.equals(Ci.nsIFactory) ||
  2161.         iid.equals(Ci.nsISupports))
  2162.       return this;
  2163.     throw Cr.NS_ERROR_NO_INTERFACE;
  2164.   },
  2165. };
  2166.  
  2167. var Module = {
  2168.   QueryInterface: function(iid) {
  2169.     if (iid.equals(Ci.nsIModule) ||
  2170.         iid.equals(Ci.nsISupports))
  2171.       return this;
  2172.  
  2173.     throw Cr.NS_ERROR_NO_INTERFACE;
  2174.   },
  2175.  
  2176.   getClassObject: function(cm, cid, iid) {
  2177.     if (!iid.equals(Ci.nsIFactory))
  2178.       throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  2179.  
  2180.     if (cid.equals(FEED_CLASSID))
  2181.       return new GenericComponentFactory(Feed);
  2182.     if (cid.equals(ITEM_CLASSID))
  2183.       return new GenericComponentFactory(FeedItem);
  2184.     if (cid.equals(FC_CLASSID))
  2185.       return new GenericComponentFactory(FeedContext);
  2186.     if (cid.equals(FF_CLASSID))
  2187.       return new GenericComponentFactory(FeedFolder);
  2188.     if (cid.equals(FM_CLASSID))
  2189.       return new GenericComponentFactory(FeedManager);
  2190.  
  2191.     throw Cr.NS_ERROR_NO_INTERFACE;
  2192.   },
  2193.  
  2194.   registerSelf: function(cm, file, location, type) {
  2195.     var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
  2196.     cr.registerFactoryLocation(FEED_CLASSID, FEED_CLASSNAME, FEED_CONTRACTID,
  2197.                                file, location, type);
  2198.     cr.registerFactoryLocation(ITEM_CLASSID, ITEM_CLASSNAME, ITEM_CONTRACTID,
  2199.                                file, location, type);
  2200.     cr.registerFactoryLocation(FC_CLASSID, FC_CLASSNAME, FC_CONTRACTID,
  2201.                                file, location, type);
  2202.     cr.registerFactoryLocation(FF_CLASSID, FF_CLASSNAME, FF_CONTRACTID,
  2203.                                file, location, type);
  2204.     cr.registerFactoryLocation(FM_CLASSID, FM_CLASSNAME, FM_CONTRACTID,
  2205.                                file, location, type);
  2206.  
  2207.     var catman = Cc['@mozilla.org/categorymanager;1']
  2208.       .getService(Ci.nsICategoryManager);
  2209.  
  2210.     catman.addCategoryEntry('flock-rdf-setup', FM_CLASSNAME,
  2211.                             'service,' + FM_CONTRACTID,
  2212.                             true, true);
  2213.  
  2214.     catman.addCategoryEntry('flockMigratable', FM_CLASSNAME, FM_CONTRACTID,
  2215.                             true, true);
  2216.     catman.addCategoryEntry('flockHousekeeping', FM_CLASSNAME, FM_CONTRACTID,
  2217.                             true, true);
  2218.   },
  2219.  
  2220.   unregisterSelf: function(cm, location, type) {
  2221.     var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
  2222.     cr.unregisterFactoryLocation(FEED_CLASSID, location);
  2223.     cr.unregisterFactoryLocation(ITEM_CLASSID, location);
  2224.     cr.unregisterFactoryLocation(FC_CLASSID, location);
  2225.     cr.unregisterFactoryLocation(FF_CLASSID, location);
  2226.     cr.unregisterFactoryLocation(FM_CLASSID, location);
  2227.   },
  2228.  
  2229.   canUnload: function(cm) {
  2230.     return true;
  2231.   },
  2232. };
  2233.  
  2234. function NSGetModule(compMgr, fileSpec)
  2235. {
  2236.   return Module;
  2237. }
  2238.